package com.mylhyl.zxing.scanner.camera;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Handler;
import android.util.Log;
import android.view.SurfaceHolder;

import com.google.zxing.PlanarYUVLuminanceSource;
import com.mylhyl.zxing.scanner.camera.open.CameraFacing;
import com.mylhyl.zxing.scanner.camera.open.OpenCamera;
import com.mylhyl.zxing.scanner.camera.open.OpenCameraInterface;

import java.io.IOException;

/**
 * This object wraps the Camera service object and expects to be the only one talking to it. The
 * implementation encapsulates the steps needed to take preview-sized images, which are used for
 * both preview and decoding.
 *
 * @author dswitkin@google.com (Daniel Switkin)
 */
public final class CameraManager {
	
	private static final String TAG = "CameraManager";
	
	private static final int MIN_FRAME_WIDTH = 240;
	private static final int MIN_FRAME_HEIGHT = 240;
	public static final int MAX_FRAME_WIDTH = 1200; // = 5/8 * 1920
	public static final int MAX_FRAME_HEIGHT = 675; // = 5/8 * 1080
	
	private final Context context;
	private final CameraConfigurationManager configManager;
	private OpenCamera camera;
	private AutoFocusManager autoFocusManager;
	private Rect framingRect;
	private Rect framingRectInPreview;
	private boolean initialized;
	private boolean previewing;
	private int requestedCameraId = OpenCameraInterface.NO_REQUESTED_CAMERA;
	private int requestedFramingRectWidth;
	private int requestedFramingRectHeight;
	/**
	 * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
	 * clear the handler so it will only receive one message.
	 */
	private final PreviewCallback previewCallback;
	private final int statusBarHeight;//状态栏高度
	private int mLaserFrameTopMargin;//扫描框离屏幕上方距离
	private boolean mScanFullScreen;
	private boolean mInvertScan;//扫描反色二维码（黑底白色码）
	
	public CameraManager(Context context, CameraFacing cameraFacing) {
		
		this.context = context;
		this.configManager = new CameraConfigurationManager(context);
		previewCallback = new PreviewCallback(configManager);
		statusBarHeight = getStatusBarHeight();
		setManualCameraId(cameraFacing == CameraFacing.BACK ? 0 : 1);
	}
	
	public Context getContext() {
		
		return context;
	}
	
	/**
	 * Opens the camera driver and initializes the hardware parameters.
	 *
	 * @param holder
	 * 	The surface object which the camera will draw preview frames into.
	 *
	 * @throws IOException
	 * 	Indicates the camera driver failed to open.
	 */
	public synchronized void openDriver(SurfaceHolder holder) throws IOException {
		
		OpenCamera theCamera = camera;
		if (theCamera == null) {
			//获取手机背面的摄像头
			theCamera = OpenCameraInterface.open(requestedCameraId);
			if (theCamera == null) {
				throw new IOException("Camera.open() failed to return object from driver");
			}
			camera = theCamera;
		}
		
		if (!initialized) {
			initialized = true;
			configManager.initFromCameraParameters(theCamera);
			if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) {
				setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight);
				requestedFramingRectWidth = 0;
				requestedFramingRectHeight = 0;
			}
		}
		
		Camera cameraObject = theCamera.getCamera();
		Camera.Parameters parameters = cameraObject.getParameters();
		String parametersFlattened = parameters == null ? null : parameters.flatten(); // Save these, temporarily
		try {
			configManager.setDesiredCameraParameters(theCamera, false, mInvertScan);
		} catch (RuntimeException re) {
			// Driver failed
			Log.i(TAG, "Camera rejected parameters. Setting only minimal safe-mode parameters");
			Log.i(TAG, "Resetting to saved camera params: " + parametersFlattened);
			// Reset:
			if (parametersFlattened != null) {
				parameters = cameraObject.getParameters();
				parameters.unflatten(parametersFlattened);
				try {
					cameraObject.setParameters(parameters);
					configManager.setDesiredCameraParameters(theCamera, true, mInvertScan);
				} catch (RuntimeException re2) {
					// Well, darn. Give up
					Log.i(TAG, "Camera rejected even safe-mode parameters! No configuration");
				}
			}
		}
		//设置摄像头预览view
		cameraObject.setPreviewDisplay(holder);
	}
	
	public synchronized boolean isOpen() {
		
		return camera != null;
	}
	
	/**
	 * Closes the camera driver if still in use.
	 */
	public synchronized void closeDriver() {
		
		if (camera != null) {
			camera.getCamera().release();
			camera = null;
			// Make sure to clear these each time we close the camera, so that any scanning rect
			// requested by intent is forgotten.
			framingRect = null;
			framingRectInPreview = null;
		}
	}
	
	/**
	 * Asks the camera hardware to begin drawing preview frames to the screen.
	 */
	public synchronized void startPreview() {
		
		OpenCamera theCamera = camera;
		if (theCamera != null && !previewing) {
			theCamera.getCamera().startPreview();
			previewing = true;
			autoFocusManager = new AutoFocusManager(theCamera.getCamera());
		}
	}
	
	/**
	 * Tells the camera to stop drawing preview frames.
	 */
	public synchronized void stopPreview() {
		
		if (autoFocusManager != null) {
			autoFocusManager.stop();
			autoFocusManager = null;
		}
		if (camera != null && previewing) {
			camera.getCamera().stopPreview();
			previewCallback.setHandler(null, 0);
			previewing = false;
		}
	}
	
	/**
	 * 设置闪光灯
	 *
	 * @param newSetting
	 * 	if {@code true}, light should be turned on if currently off. And vice
	 * 	versa.
	 */
	public synchronized void setTorch(boolean newSetting) {
		
		OpenCamera theCamera = camera;
		if (theCamera != null && newSetting != configManager.getTorchState(theCamera.getCamera())) {
			boolean wasAutoFocusManager = autoFocusManager != null;
			if (wasAutoFocusManager) {
				autoFocusManager.stop();
				autoFocusManager = null;
			}
			configManager.setTorch(theCamera.getCamera(), newSetting);
			if (wasAutoFocusManager) {
				autoFocusManager = new AutoFocusManager(theCamera.getCamera());
				autoFocusManager.start();
			}
		}
	}
	
	/**
	 * A single preview frame will be returned to the handler supplied. The data will arrive as
	 * byte[]
	 * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
	 * respectively.
	 * <br/>
	 * <p/>
	 * 1：将handler与preview回调函数绑定；<br/>
	 * 2：注册preview回调函数<br/>
	 * 综上，该函数的作用是当相机的预览界面准备就绪后就会调用handler向其发送传入的message
	 *
	 * @param handler
	 * 	The handler to send the message to.
	 * @param message
	 * 	The what field of the message to be sent.
	 */
	public synchronized void requestPreviewFrame(Handler handler, int message) {
		
		OpenCamera theCamera = camera;
		if (theCamera != null && previewing) {
			previewCallback.setHandler(handler, message);
			theCamera.getCamera().setOneShotPreviewCallback(previewCallback);
		}
	}
	
	/**
	 * Calculates the framing rect which the UI should draw to show the user where to place the
	 * barcode. This target helps with alignment as well as forces the user to hold the device
	 * far enough away to ensure the image will be in focus.
	 *
	 * @return The rectangle to draw on screen in window coordinates.
	 */
	public synchronized Rect getFramingRect() {
		
		if (framingRect == null) {
			if (camera == null) {
				return null;
			}
			Point screenResolution = configManager.getScreenResolution();
			if (screenResolution == null) {
				// Called early, before init even finished
				return null;
			}
			int height;
			int width = findDesiredDimensionInRange(screenResolution.x, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH);
			//竖屏则为正方形
			if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
				height = width;
			} else {
				height = findDesiredDimensionInRange(screenResolution.y, MIN_FRAME_HEIGHT, MAX_FRAME_HEIGHT);
			}
			int leftOffset = (screenResolution.x - width) / 2;
			int topOffset = (screenResolution.y - height) / 2;
			if (mLaserFrameTopMargin == 0)
				mLaserFrameTopMargin = topOffset - statusBarHeight;
			else {
				mLaserFrameTopMargin += statusBarHeight;
			}
			framingRect = new Rect(leftOffset, mLaserFrameTopMargin, leftOffset + width, mLaserFrameTopMargin + height);
			Log.i(TAG, "Calculated framing rect: " + framingRect);
		}
		return framingRect;
	}
	
	private static int findDesiredDimensionInRange(int resolution, int hardMin, int hardMax) {
		
		int dim = 5 * resolution / 8; // Target 5/8 of each dimension
		if (dim < hardMin) {
			return hardMin;
		}
		if (dim > hardMax) {
			return hardMax;
		}
		return dim;
	}
	
	/**
	 * Like {@link #getFramingRect} but coordinates are in terms of the preview frame,
	 * not UI / screen.
	 *
	 * @return {@link Rect} expressing barcode scan area in terms of the preview size
	 */
	public synchronized Rect getFramingRectInPreview() {
		
		if (framingRectInPreview == null) {
			Rect framingRect = getFramingRect();
			if (framingRect == null) {
				return null;
			}
			Rect rect = new Rect(framingRect);
			Point cameraResolution = configManager.getCameraResolution();
			Point screenResolution = configManager.getScreenResolution();
			if (cameraResolution == null || screenResolution == null) {
				// Called early, before init even finished
				return null;
			}
			
			//竖屏识别一维
			if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
				rect.left = rect.left * cameraResolution.y / screenResolution.x;
				rect.right = rect.right * cameraResolution.y / screenResolution.x;
				rect.top = rect.top * cameraResolution.x / screenResolution.y;
				rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
			} else {
				rect.left = rect.left * cameraResolution.x / screenResolution.x;
				rect.right = rect.right * cameraResolution.x / screenResolution.x;
				rect.top = rect.top * cameraResolution.y / screenResolution.y;
				rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
			}
			framingRectInPreview = rect;
		}
		return framingRectInPreview;
	}
	
	/**
	 * Allows third party apps to specify the camera ID, rather than determine
	 * it automatically based on available cameras and their orientation.
	 *
	 * @param cameraId
	 * 	camera ID of the camera to use. A negative value means "no preference".
	 */
	public synchronized void setManualCameraId(int cameraId) {
		
		requestedCameraId = cameraId;
	}
	
	/**
	 * Allows third party apps to specify the scanning rectangle dimensions, rather than determine
	 * them automatically based on screen resolution.
	 *
	 * @param width
	 * 	The width in pixels to scan.
	 * @param height
	 * 	The height in pixels to scan.
	 */
	public synchronized void setManualFramingRect(int width, int height) {
		
		if (initialized) {
			Point screenResolution = configManager.getScreenResolution();
			if (width > screenResolution.x) {
				width = screenResolution.x;
			}
			if (height > screenResolution.y) {
				height = screenResolution.y;
			}
			
			int leftOffset = (screenResolution.x - width) / 2;
			int topOffset = (screenResolution.y - height) / 2 - statusBarHeight;
			if (mLaserFrameTopMargin == 0)
				mLaserFrameTopMargin = topOffset;
			else {
				mLaserFrameTopMargin += statusBarHeight;
			}
			framingRect = new Rect(leftOffset, mLaserFrameTopMargin, leftOffset + width, mLaserFrameTopMargin + height);
			//   Log.i(TAG, "Calculated manual framing rect: " + framingRect);
			framingRectInPreview = null;
		} else {
			requestedFramingRectWidth = width;
			requestedFramingRectHeight = height;
		}
	}
	
	/**
	 * A factory method to build the appropriate LuminanceSource object based on the format
	 * of the preview buffers, as described by Camera.Parameters.
	 *
	 * @param data
	 * 	A preview frame.
	 * @param width
	 * 	The width of the image.
	 * @param height
	 * 	The height of the image.
	 *
	 * @return A PlanarYUVLuminanceSource instance.
	 */
	public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
		
		if (mScanFullScreen) {
			return new PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false);
		}
		Rect rect = getFramingRectInPreview();
		if (rect == null) {
			return null;
		}
		// Go ahead and assume it's YUV rather than die.
		return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, rect.width(), rect.height(),
			false);
	}
	
	/**
	 * 获取状态栏高度
	 *
	 * @return
	 */
	private int getStatusBarHeight() {
		
		int result = 0;
		int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
		if (resourceId > 0) {
			result = context.getResources().getDimensionPixelSize(resourceId);
		}
		return result;
	}
	
	/**
	 * 设置扫描框与屏幕上方距离
	 *
	 * @param laserFrameTopMargin
	 */
	public void setLaserFrameTopMargin(int laserFrameTopMargin) {
		
		this.mLaserFrameTopMargin = laserFrameTopMargin;
	}
	
	public void setScanFullScreen(boolean scanFullScreen) {
		
		this.mScanFullScreen = scanFullScreen;
	}
	
	public void setInvertScan(boolean invertScan) {
		
		this.mInvertScan = invertScan;
	}
}